package com.yixing.tech.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;


/**
 * @author <a href="mailto:brucezhang_jjz@163.com">zhangjun</a>
 * @since 1.0.0
 */
@Slf4j
public abstract class MongoUtils {

    private static final DateTimeFormatter[] DATE_FORMATTERS = new DateTimeFormatter[]{
            DateTimeFormatter.ofPattern("yyyy-M-d"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd"),
    };

    private static final DateTimeFormatter[] DATE_TIME_FORMATTERS = new DateTimeFormatter[]{
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"),
            DateTimeFormatter.ofPattern("yyyy-M-dd H:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSSSXXX"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy/M/d H:m:s"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"),
    };

    public static String safeGetDocId(Document doc) {
        if(doc == null) {
            return null;
        }
        Object id = doc.get("id");
        if(id == null) {
            id = doc.get("_id");
        }
        if(id == null) {
            return null;
        } else {
            return id.toString();
        }
    }


    public static <T> void canCastThen(Class<T> c, Object obj, Consumer<T> then) {
        if (c.isInstance(obj)) {
            then.accept((T) obj);
        } else {
            if (obj != null) {
                log.error("canCastThen obj: {} class: {}", obj, c);
            }
        }
    }

    public static void canBeDouble(Object obj, Consumer<Double> then) {
        if (obj == null) {
            return;
        }
        if (obj instanceof Double) {
            then.accept((Double) obj);
        } else if (obj instanceof Integer) {
            then.accept(((Integer) obj).doubleValue());
        } else if (obj instanceof Long) {
            then.accept(((Long) obj).doubleValue());
        } else if (obj instanceof String) {
            then.accept(Double.parseDouble((String) obj));
        } else {
            try {
                then.accept(((Double) obj));
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }

    public static Double safeGetDouble(Document doc, String attr) {
        return safeGetDouble(doc, attr, null);
    }

    public static Double safeGetDouble(Object obj, Double defaultValue) {
        if (obj == null) {
            return defaultValue;
        }
        if (obj instanceof Double) {
            return (Double) obj;
        } else if (obj instanceof Integer) {
            return ((Integer) obj).doubleValue();
        } else if (obj instanceof Long) {
            return ((Long) obj).doubleValue();
        } else if (obj instanceof String) {
            return Double.parseDouble((String) obj);
        } else {
            try {
                return (Double) obj;
            } catch (Throwable e) {
                return defaultValue;
            }
        }
    }

    public static void updateDoc(Document doc, String key, Object val, Function<Object, Object> ifPresent) {
        if (!doc.containsKey(key)) {
            doc.put(key, val);
        } else {
            doc.put(key, ifPresent.apply(val));
        }
    }

    public static String safeCalRate(double num1, double num2, int retain) {
        if (num2 == 0D) {
            return "0%";
        }
        double num = num1 / num2;
        return safeTransferRate(num, retain);
    }

    public static String safeTransferRate(double num, int retain) {
        if (retain < 2) {
            return num * 100 + "%";
        }
        int numPow = (int) (num * Math.pow(10, retain));
        int numPow2 = (int) (num * Math.pow(10, retain + 1)) - (numPow * 10);
        if (numPow2 >= 5) {
            numPow += 1;
        }
        int pow = (int) Math.pow(10, retain - 2);
        int zs = numPow / pow;
        int xs = numPow % pow;
        if (xs > 0) {
            return zs + "." + String.format("%" + (retain - 2) + "s", xs).replace(' ', '0') + "%";
        } else {
            return zs + "%";
        }
    }

    public static Double safeResolveDouble(Document doc, String attr, Double defaultValue) {
        if (doc == null) {
            return defaultValue;
        }
        Object obj = doc.get(attr);
        if (obj == null) {
            return defaultValue;
        }
        if (obj instanceof Double) {
            return (Double) obj;
        } else if (obj instanceof BigDecimal) {
            return ((BigDecimal) obj).doubleValue();
        } else if (obj instanceof Integer) {
            return ((Integer) obj).doubleValue();
        } else if (obj instanceof Long) {
            return ((Long) obj).doubleValue();
        } else if (obj instanceof String) {
            try {
                String trim = ((String) obj).replace("%", "").trim();
                return Double.parseDouble(trim);
            } catch (Throwable e) {
                return defaultValue;
            }
        } else {
            try {
                return (Double) obj;
            } catch (Throwable e) {
                log.error("文档[{}] 属性[{}] 转不了Double", doc, attr, e);
                return defaultValue;
            }
        }
    }

    public static Double safeResolveDoubleFromMap(Map doc, String attr, Double defaultValue) {
        if (doc == null) {
            return defaultValue;
        }
        Object obj = doc.get(attr);
        if (obj == null) {
            return defaultValue;
        }
        if (obj instanceof Double) {
            return (Double) obj;
        } else if (obj instanceof BigDecimal) {
            return ((BigDecimal) obj).doubleValue();
        } else if (obj instanceof Integer) {
            return ((Integer) obj).doubleValue();
        } else if (obj instanceof Long) {
            return ((Long) obj).doubleValue();
        } else if (obj instanceof String) {
            try {
                String trim = ((String) obj).replace("%", "").trim();
                return Double.parseDouble(trim);
            } catch (Throwable e) {
                return defaultValue;
            }
        } else {
            try {
                return (Double) obj;
            } catch (Throwable e) {
                log.error("文档[{}] 属性[{}] 转不了Double", doc, attr, e);
                return defaultValue;
            }
        }
    }

    public static Double safeGetDouble(Document doc, String attr, Double defaultValue) {
        if (doc == null) {
            return defaultValue;
        }
        Object obj = doc.get(attr);
        if (obj == null) {
            return defaultValue;
        }
        if (obj instanceof Double) {
            return (Double) obj;
        } else if (obj instanceof BigDecimal) {
            return ((BigDecimal) obj).doubleValue();
        } else if (obj instanceof Integer) {
            return ((Integer) obj).doubleValue();
        } else if (obj instanceof Long) {
            return ((Long) obj).doubleValue();
        } else if (obj instanceof String) {
            try {
                return Double.parseDouble((String) obj);
            } catch (Throwable e) {
                return defaultValue;
            }
        } else {
            try {
                return (Double) obj;
            } catch (Throwable e) {
                log.error("文档[{}] 属性[{}] 转不了Double", doc, attr, e);
                return defaultValue;
            }
        }
    }

    public static Double safeGetDoubleByMap(Map doc, Object attr, Double defaultValue) {
        if (doc == null) {
            return defaultValue;
        }
        Object obj = doc.get(attr);
        if (obj == null) {
            return defaultValue;
        }
        if (obj instanceof Double) {
            return (Double) obj;
        } else if (obj instanceof Integer) {
            return ((Integer) obj).doubleValue();
        }  else if (obj instanceof BigDecimal) {
            return ((BigDecimal) obj).doubleValue();
        } else if (obj instanceof Long) {
            return ((Long) obj).doubleValue();
        } else if (obj instanceof String) {
            return Double.parseDouble((String) obj);
        } else {
            try {
                return (Double) obj;
            } catch (Throwable e) {
                log.error("文档[{}] 属性[{}] 转不了Double", doc, attr);
                return defaultValue;
            }
        }
    }

    public static Date safeGetDate(Document doc, String attr, Date defaultValue) {
        if (doc == null) {
            return defaultValue;
        }
        Object obj = doc.get(attr);
        if (obj == null) {
            return defaultValue;
        }
        if (obj instanceof Date) {
            return (Date) obj;
        } else if (obj instanceof Integer) {
            return new Date(((Integer) obj).longValue());
        } else if (obj instanceof Long) {
            return new Date((Long) obj);
        } else if (obj instanceof String) {
            String dateStr =  (String) obj;
            // 匹配日期
            int tryIndex = 0;
            while (tryIndex < DATE_FORMATTERS.length) {
                try {
                    return Date.from(LocalDate.parse(dateStr, DATE_FORMATTERS[tryIndex])
                            .atTime(0, 0, 0)
                            .atZone(ZoneOffset.systemDefault()).toInstant());
                } catch (Throwable e) {
                    tryIndex += 1;
                }
            }

            // 匹配日期时间
            tryIndex = 0;
            while (tryIndex < DATE_TIME_FORMATTERS.length) {
                try {
                    return Date.from(LocalDateTime.parse(dateStr, DATE_TIME_FORMATTERS[tryIndex])
                            .atZone(ZoneOffset.systemDefault()).toInstant());
                } catch (Throwable e) {
                    tryIndex += 1;
                }
            }
            return defaultValue;
        } else {
            try {
                return (Date) obj;
            } catch (Throwable e) {
                log.error("文档[{}] 属性[{}] 转不了Double", doc, attr, e);
                return defaultValue;
            }
        }
    }

    public static <T> List<T> safeGetList(Map doc, String attr, Class<T> clazz) {
        if (doc == null) {
            return Collections.emptyList();
        }
        Object obj = doc.get(attr);
        if (obj == null) {
            return Collections.emptyList();
        }
        if (obj instanceof List) {
            try {
                return (List<T>) obj;
            } catch (Throwable ignore) {
                log.error("safeGetList error, Doc: {}, attr: {}, class: {}", doc, attr, clazz, ignore);
                return Collections.emptyList();
            }
        } else {
            return Collections.emptyList();
        }
    }

    public static String formatRateStr(double source, int decimalPlaces) {
        // 参数校验
        if (decimalPlaces < 0) {
            throw new IllegalArgumentException("小数位数不能为负数");
        }

        // 限制最大小数位数
        decimalPlaces = Math.min(decimalPlaces, 8);

        // 构建格式模板
        String pattern = "#";
        if (decimalPlaces > 0) {
            pattern += "." + repeatZeros(decimalPlaces);
        }
        DecimalFormat df = new DecimalFormat(pattern);
        df.setRoundingMode(RoundingMode.HALF_UP);
        return df.format(source);
    }

    public static Criteria newCriteria() {
        return Criteria.where("isDelete").ne(1);
    }

    public static Query simpleQuery(Object...  args) {
        Criteria criteria = newCriteria();
        for (int i = 0; i < args.length - 1; i++) {
            criteria.and(args[i] + "").is(args[i + 1]);
        }
        return Query.query(criteria);
    }

    public static Query queryId(String id) {
        Asserts.notBlank(id);
        Criteria criteria = newCriteria();
        criteria.and("_id").is(new ObjectId(id));
        return Query.query(criteria);
    }

    private static String repeatZeros(int count) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append('#');
        }
        return sb.toString();
    }

    public static Map safeGetMap(Map map, String attr) {
        if (map == null) {
            return Collections.emptyMap();
        }
        Object val = map.get(attr);
        if (val == null) {
            return Collections.emptyMap();
        }
        return (Map) val;
    }

    public static String safeGetString(Map map, String attr) {
        return safeGetString(map, attr, null);
    }

    public static String safeGetString(Map map, String attr, String defaultValue) {
        if (map == null) {
            return defaultValue;
        }
        Object val = map.get(attr);
        if (val == null) {
            return defaultValue;
        }
        return String.valueOf(val);
    }

    public static Function<Long, String> timeBut = time -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time));
    public static Function<String, Date> timeParse = time -> {
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(time);
        } catch (ParseException e) {
            log.error("时间转换错误[{}]", time, e.getMessage());
            return null;
        }
    };

    /**
     * 根据时间戳获取时间间隔，根据班次分钟动态判断
     */
    public static int getHourGroupKey2(long ts, int shiftMin) {
        ZonedDateTime dateTime = Instant.ofEpochMilli(ts).atZone(ZoneId.systemDefault());
        int hour = dateTime.getHour();
        int minute = dateTime.getMinute();
        int result = minute >= shiftMin ? hour + 1 : hour;
        if (result >= 24) {
            result = 0;
        }
        return result;
    }

    /**
     * 根据时间戳 和 班次开始分时刻 计算出 当前所处的 开始小时时刻
     * shiftMin = 30, ts = 14:30 ==> 14
     * shiftMin = 30, ts = 14:29 ==> 13
     */
    public static int getStartHourGroupKey(long ts, int shiftMin) {
        ZonedDateTime dateTime = Instant.ofEpochMilli(ts).atZone(ZoneId.systemDefault());
        int minute = dateTime.getMinute();
        if (minute >= shiftMin) {
           return dateTime.getHour();
        } else {
            return dateTime.minusHours(-1).getHour();
        }
    }

    /**
     * 根据时间戳 和 班次开始分时刻 计算出 当前所处的 开始小时时刻
     * shiftMin = 30, ts = 14:30 ==> [14,15]
     * shiftMin = 30, ts = 14:29 ==> [13,14]
     */
    public static int[] getStartEndHourGroupKey(long ts, int shiftMin) {
        ZonedDateTime dateTime = Instant.ofEpochMilli(ts).atZone(ZoneId.systemDefault());
        int minute = dateTime.getMinute();
        if (minute >= shiftMin) {
            return new int[]{dateTime.getHour(), dateTime.plusHours(1).getHour()};
        } else {
            return new int[]{dateTime.plusHours(-1).getHour(), dateTime.getHour()};
        }
    }

    public static String getShiftName(Long ts, List<Document> shifts) {
        LocalDateTime localDateTime = Instant.ofEpochMilli(ts)
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        int hour = localDateTime.getHour();
        int minute = localDateTime.getMinute();
        int totalMinutes = hour * 60 + minute;
        for (Document document : shifts) {
            Document shiftDoc = (Document) document.get("班次");
            String currentShiftId = shiftDoc.getString("id");
            double startHour = document.getDouble("开始小时");
            double startMin = document.getDouble("开始分钟");
            double endHour = document.getDouble("结束小时");
            double endMin = document.getDouble("结束分钟");
            double shiftStart = startHour * 60 + startMin;
            double shiftEnd = endHour * 60 + endMin;
            if (shiftStart > shiftEnd) {
                if (totalMinutes >= shiftStart || totalMinutes < shiftEnd) {
                    return "夜班";
                }
            } else {
                if (totalMinutes >= shiftStart && totalMinutes < shiftEnd) {
                    return "白班";
                }
            }
        }
        return "夜班";
    }

    // 筛选班次所在的时间范围
    public static List<Long[]> getShiftPeriods(long startTs, long endTs, List<Document> shifts,
                                               int shiftMin, String shiftFilter) {
        if (shiftFilter == null || shiftFilter.isEmpty()) {
            return Collections.singletonList(new Long[]{startTs, endTs});
        }
        List<Long[]> result = new ArrayList<>();
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDateTime startDate = Instant.ofEpochMilli(startTs).atZone(zoneId).toLocalDateTime();

        // 修正至半点
        LocalDateTime fixedStartDate = startDate.withMinute(30).withSecond(0).withNano(0);
        if (startDate.isAfter(fixedStartDate)) {
            // 修正至下一个小时的整点时刻
            fixedStartDate = startDate.plusHours(1).withMinute(0).withSecond(0).withNano(0);
        }
        long tsStep = fixedStartDate.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
        long split = shiftMin * 60 * 1000L;
        String shiftName = getShiftName(startTs, shifts);
        if (Objects.equals(shiftName, shiftFilter)) {
            Long[] longs = new Long[2];
            longs[0] = startTs;
            tsStep = Math.min((tsStep + split), endTs);
            longs[1] = tsStep;
            result.add(longs);
        }

        while (tsStep < endTs) {
            long nextStep = Math.min((tsStep + split), endTs);
            shiftName = getShiftName(tsStep, shifts);
            if (Objects.equals(shiftName, shiftFilter)) {
                Long[] longs;
                if (!result.isEmpty()) {
                    longs = result.get(result.size() - 1);
                } else {
                    longs = new Long[2];
                    longs[0] = tsStep;
                    longs[1] = nextStep;
                    result.add(longs);
                    continue;
                }
                long end = longs[1];
                if (end == tsStep - split || end == tsStep) {
                    longs[1] = nextStep;
                } else {
                    Long[] longs2 = new Long[2];
                    longs2[0] = tsStep;
                    longs2[1] = nextStep;
                    result.add(longs2);
                }
            }
            tsStep = nextStep;
        }
        return result;
    }

    /**
     * 计算标准差（基于数组，自动计算均值）
     *
     * @param data double 数组
     * @return 标准差（若数据为空返回 NaN）
     */
    public static double calculateStdDev(List<Double> data) {
        if (data == null || data.isEmpty()) {
            return Double.NaN;
        }

        // 计算均值
        double mean = 0.0;
        for (double value : data) {
            mean += value;
        }
        mean /= data.size();

        // 计算平方差和
        double sumSquaredDiff = 0.0;
        for (double value : data) {
            sumSquaredDiff += Math.pow(value - mean, 2);
        }

        // 计算标准差（总体标准差，分母用 N）
        return Math.sqrt(sumSquaredDiff / data.size());
    }

    public static List<LocalDateTime> resolveStartTime(Long startTs, Long endTs, Integer[] shiftStart) {
        LocalDateTime localStart = LocalDateTime.ofInstant(Instant.ofEpochMilli(startTs), ZoneOffset.systemDefault());
        LocalDateTime localEnd = LocalDateTime.ofInstant(Instant.ofEpochMilli(endTs), ZoneOffset.systemDefault());

        if (localStart.getHour() <= shiftStart[0] && localStart.getMinute() < shiftStart[1]) {
            localStart = localStart.minusDays(1);
        }
        localStart = localStart.withHour(shiftStart[0]).withMinute(shiftStart[1]).withSecond(0).withNano(0);

        if (localEnd.getHour() >= shiftStart[0] && localEnd.getMinute() >= shiftStart[1]) {
            localEnd = localStart.plusDays(1);
        }
        localEnd = localEnd.withHour(shiftStart[0]).withMinute(shiftStart[1]).withSecond(0).withNano(0);

        List<LocalDateTime> days = new ArrayList<>();
        while (localStart.isBefore(localEnd)) {
            days.add(localStart);
            localStart = localStart.plusDays(1);
        }
        return days;
    }

    public static <T> List<T> sparsifyList(List<T> list, int targetSize) {
        if (list == null) {
            return Collections.emptyList();
        }
        if (list.size() <= targetSize) {
            return new ArrayList<>(list);
        }

        List<T> sparseList = new ArrayList<>(targetSize);
        double step = (double) list.size() / targetSize;

        for (int i = 0; i < targetSize; i++) {
            int index = (int) Math.round(i * step);
            // 确保不越界
            index = Math.min(index, list.size() - 1);
            sparseList.add(list.get(index));
        }

        return sparseList;
    }

    public static String formatNumber(String value, int decimalPlaces) {
        try {
            if (value.contains("%")) {
                // 处理百分数
                String numberPart = value.replace("%", "").trim();
                BigDecimal number = new BigDecimal(numberPart);

                // 格式化为指定位数的小数
                number = number.setScale(decimalPlaces, RoundingMode.HALF_UP);

                // 添加百分号并返回
                return number.toString() + "%";
            } else {
                // 处理纯数字
                BigDecimal number = new BigDecimal(value);

                // 格式化为指定位数的小数
                number = number.setScale(decimalPlaces, RoundingMode.HALF_UP);

                return number.toString();
            }
        } catch (NumberFormatException e) {
            // 解析失败时返回原始值
            return value;
        }
    }

}
